package p48_事件监听器;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;

/**
 * 上一节演示了可以通过@EventListener标注方法来实现了监听器方法
 * 那么这种方式的原理是什么呢?
 * <p>
 * 本节知识点：
 * classA.isAssignableFrom(classB)，如果classB对应的对象能赋给classA对应的类型，则返回true
 */
@Configuration
@Slf4j
public class _3_EventListener注解原理 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(_3_EventListener注解原理.class);
        /**
         * 自定义注解实现监听器的原理:
         *  1. 在容器创建后，遍历所有bean的所有方法，判断方法是否标注了@MyListener
         *  2. 如果标注了@MyListener，就创建ApplicationListener实现类，然后在实现方法里间接调用该方法
         *  3. 把创建的ApplicationListener实现类注册进监听器
         * 那么以上操作的代码应该写在哪呢？
         *  前提：保证容器所有bean初始化完毕后，才能开始执行上诉操作
         *  已知：spring有一个特殊的bean:SmartInitializingSingleton，
         *          只有容器中所有单例bean都初始化之后，才会初始化SmartInitializingSingleton类型的bean
         *  所以：可以将上诉操作放进SmartInitializingSingleton内部
         */

        context.getBean(_3_EventListener注解原理.MyService.class).doService();
        context.close();
    }

    /**
     * 解析@MyListener的操作得放在这，原因上面提到了
     * 也可以写在某个后处理器中，根据后处理器导入与否来开启关闭@MyListener
     */
    @Bean
    public SmartInitializingSingleton smartInitializingSingleton(AnnotationConfigApplicationContext context) {
        //SmartInitializingSingleton是一个函数时接口，初始化该类bean时就会回调afterSingletonsInstantiated()
        SmartInitializingSingleton smartInitializingSingleton = () -> {
            //遍历所有bean的所有方法
            for (String name : context.getBeanDefinitionNames()) {
                Object bean = context.getBean(name);
                for (Method method : bean.getClass().getDeclaredMethods()) {
                    //如果标注了@MyListener，并且方法至少有一个参数
                    if (method.isAnnotationPresent(MyListener.class) && method.getParameterTypes().length > 0) {

                        ApplicationListener listener = realEvent -> {
                            Class<?> targetType = method.getParameterTypes()[0];
                            //只有当前监听器感兴趣的事件targetType与真实发生的事件匹配
                            if (targetType.isAssignableFrom(realEvent.getClass())) {
                                try {
                                    //间接调用该方法
                                    method.invoke(bean, realEvent);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        };
                        //向容器添加监听器
                        context.addApplicationListener(listener);
                    }
                }
            }
        };
        return smartInitializingSingleton;
    }

    /**
     * 使用这个注解，来模拟@EventListener
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @interface MyListener {
    }

    /**
     * 下面代码跟上一节类似，都定义了一个业务方法来发布事件，两个监听器来监听事件，只不过监听器使用自定义注解MyListener
     * 事件对象复用了第一节的
     */
    @Component
    static class MyService {
        @Autowired
        private ApplicationEventPublisher publisher;

        public void doService() {
            publisher.publishEvent(new _1_基于接口的监听器.SmsEvent("MyService.doService"));
            publisher.publishEvent(new _1_基于接口的监听器.EmailEvent("MyService.doService"));
        }
    }

    @Component
    static class SmsListener {
        @MyListener
        public void listener(_1_基于接口的监听器.SmsEvent event) throws InterruptedException {
            log.info("正在发送短信...");
            log.info("发送短信成功!!!");
        }
    }

    @Component
    static class EmailListener {
        @MyListener
        public void listener(_1_基于接口的监听器.EmailEvent event) throws InterruptedException {
            log.info("正在发送邮件...");
            log.info("发送邮件成功!!!");
        }
    }
}
